#include "pch.h"
#include "dxroids.h"
#include "Application.h"
#include "GameSettings.h"
#include "Random.h"

#include <algorithm>
#include <iterator>

D2D1_POINT_2F dxroids::ScreenSize;

namespace
{  
   char draw_score_buffer_[32];

   dxroids::Vector VectorWithMagnitudeAndDirection(float _magnitude, float _dmin, float _dmax)
   {
      dxroids::Random rand;

      // top dead center
      dxroids::Vector vect(0, -_magnitude);
      float const direction = rand.NextFloat(_dmin, _dmax);
      DxRoidsAssert(direction >= _dmin);
      DxRoidsAssert(direction <= _dmax);
      vect.Rotate(direction);
      return vect;
   }   
}

DXROIDS_COM_PTR<ID2D1Factory> const & dxroids::GetDirect2DFactory()
{
   static DXROIDS_COM_PTR<ID2D1Factory> factory_;
   if (factory_ == nullptr)
   {
      D2D1_FACTORY_OPTIONS options;
      options.debugLevel = D2D1_DEBUG_LEVEL_INFORMATION;
      D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED, options, &factory_);
   }
   return factory_;
}

// Message handler for about box.
INT_PTR CALLBACK About(HWND _dialog_handle, UINT _message, WPARAM wParam, LPARAM lParam)
{
   UNREFERENCED_PARAMETER(lParam);
   switch (_message)
   {
   case WM_INITDIALOG:
      return (INT_PTR)TRUE;

   case WM_COMMAND:
      if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
      {
         EndDialog(_dialog_handle, LOWORD(wParam));
         return (INT_PTR)TRUE;
      }
      break;
   }
   return (INT_PTR)FALSE;
}


dxroids::Application::Application()
   : title_bar_text_(nullptr)
   , main_windows_class_name_(nullptr)   
   , window_handle_(nullptr)
   , gamepad_user_index_(-1)
   , frame_counter_(0)
   , next_thump_(0)   
   , high_score_(LoadHighScore())
{
   int32_t const MaxLoadStringLength = 256;
   title_bar_text_.reset(new TCHAR[MaxLoadStringLength]);
   main_windows_class_name_.reset(new TCHAR[MaxLoadStringLength]);

   LoadString(HINST_THISCOMPONENT, IDS_APP_TITLE, title_bar_text_.get(), MaxLoadStringLength);
   LoadString(HINST_THISCOMPONENT, IDC_ASTEROIDS, main_windows_class_name_.get(), MaxLoadStringLength);  

   thump_interval_ = static_cast<int32_t>(DXROIDS_APPLICATION_FRAMES_PER_SECOND /DXROIDS_APPLICATION_THUMPS_PER_SECOND);

   // srand(static_cast<unsigned int>(time(nullptr)));
}

#if 0
dxroids::Application::~Application()
{
   if (player_ship_ != nullptr)
   {
      player_ship_.release();
   }
   if (enemy_ship_ != nullptr)
   {
      enemy_ship_.release();
   }
  
   remaining_ships_.clear();
   rocks_.clear();
   explosions_.clear();

   primary_brush_.Release();
   secondary_brush_.Release();
   debug_brush_.Release();
   render_target_.Release();
}
#endif

int32_t dxroids::Application::LoadHighScore()
{
   return 10230;
}



void dxroids::Application::AddRocks(dxroids::SpaceRock::Pair & _new_rocks, std::vector<std::unique_ptr<dxroids::SpaceRock>> & _list)
{
   if (_new_rocks.first != nullptr)
   {
      _list.push_back(std::move(_new_rocks.first));
   }
   if (_new_rocks.second != nullptr)
   {
      _list.push_back(std::move(_new_rocks.second));
   }
}  

void dxroids::Application::CheckCollision(dxroids::Ship & _ship, dxroids::SpaceRock & _rock, std::vector<std::unique_ptr<dxroids::SpaceRock>> & _new_rocks)
{
   if (_ship.IsShotHittingObject(_rock))
   {
      explosions_.push_back(std::move(Explosion::NewDefault(_rock.GetPosition())));
      AddRocks(_rock.Break(sound_manager_), _new_rocks);
      player_.AddToScore(_rock);
   }
   if (_rock.IntersectsWith(_ship))
   {
      explosions_.push_back(std::move(Explosion::NewDefault(_rock.GetPosition())));
      AddRocks(_rock.Break(sound_manager_), _new_rocks);
      explosions_.push_back(_ship.Destroy(sound_manager_));
   }   
}

HRESULT dxroids::Application::Initialize()
{
   HRESULT result = CreateDeviceIndependentResources();
   if (SUCCEEDED(result))
   {
      WNDCLASSEX wcex;
      ZeroMemory(&wcex, sizeof(wcex));

      wcex.cbSize = sizeof(WNDCLASSEX);
      wcex.style = CS_HREDRAW | CS_VREDRAW;
      wcex.lpfnWndProc = dxroids::Application::MessageHandler;
      wcex.cbClsExtra = 0;
      wcex.cbWndExtra = sizeof(LONG_PTR);
      wcex.hInstance = HINST_THISCOMPONENT;
      wcex.hIcon = LoadIcon(HINST_THISCOMPONENT, MAKEINTRESOURCE(IDI_ASTEROIDS));
      wcex.hCursor = LoadCursor(NULL, IDC_ARROW);
      wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
      wcex.lpszMenuName = MAKEINTRESOURCE(IDC_ASTEROIDS);
      wcex.lpszClassName = main_windows_class_name_.get();
      wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));

      RegisterClassEx(&wcex);   

      FLOAT dpi_x, dpi_y;

      GetDirect2DFactory()->GetDesktopDpi(&dpi_x, &dpi_y);

      UINT width = static_cast<UINT>(::ceil(DXROIDS_APPLICATION_SCREEN_WIDTH * dpi_x / DXROIDS_APPLICATION_DPI));
      UINT height = static_cast<UINT>(::ceil(DXROIDS_APPLICATION_SCREEN_HEIGHT * dpi_y / DXROIDS_APPLICATION_DPI));

      window_handle_ = CreateWindow(main_windows_class_name_.get(), L"Asteroids", WS_OVERLAPPEDWINDOW,
         CW_USEDEFAULT, CW_USEDEFAULT, width, height, nullptr, nullptr, HINST_THISCOMPONENT, this);

      ScreenSize.x = static_cast<float>(width);
      ScreenSize.y = static_cast<float>(height);

      result = window_handle_ ? S_OK : E_FAIL;
      if (SUCCEEDED(result))
      {

#ifdef DXROIDS_GAMEPAD_ENABLED
         for (int i = 0; i < XUSER_MAX_COUNT; ++i)
         {            
            XINPUT_STATE state;
            ZeroMemory(&state, sizeof(XINPUT_STATE));
            DWORD const result = XInputGetState(i, &state);
            if (ERROR_SUCCESS == result)
            {
               gamepad_user_index_ = i;
               break;               
            }
         }
#endif

         ShowWindow(window_handle_, SW_SHOWNORMAL);
         UpdateWindow(window_handle_);
         LoadAccelerators(HINST_THISCOMPONENT, MAKEINTRESOURCE(IDC_ASTEROIDS));
      }      
   }

   if (sound_manager_ == nullptr)
   {
      sound_manager_.reset(new SoundManager());
      if (!GameSettings::Instance().SoundEnabled())
      {
         sound_manager_->Mute();
      }
   }

   if (!SUCCEEDED(sound_manager_->Initialize(window_handle_)))
   {
      // NO SOUND
   }   

   player_ship_.reset(new PlayerShip(Vector(ScreenSize.x / 2.0f, ScreenSize.y / 2.0f)));   

   // player_ship_.reset(new NoahsShip(Vector(ScreenSize.x / 2.0f, ScreenSize.y / 2.0f), 0.3f));
   player_ship_->Initialize(GetDirect2DFactory());   
   player_ship_->SetIsActive(true);

   Vector remaining_ships_pos;
   remaining_ships_pos.SetX(233);
   remaining_ships_pos.SetY(60);

   for (int i = 0; i < 3; ++i)
   {
      std::unique_ptr<PlayerShip> ship(new PlayerShip(remaining_ships_pos, 50.0f, 0.7f));
      ship->Initialize(GetDirect2DFactory());
      ship->SetIsActive(true);
      remaining_ships_.push_back(std::move(ship));
      remaining_ships_pos.SetX(remaining_ships_pos.GetX() - 25);
   }

   for (int i = 0; i < 3; ++i)
   {
      rocks_.push_back(std::move(NewRock()));
   }   

   return S_OK;
}


void dxroids::Application::PlaySoundEffect(SoundEffect _effect, bool _loop)
{   
   sound_manager_->Play(_effect, _loop);   
}

void dxroids::Application::StopSoundEffect(SoundEffect _effect)
{
   sound_manager_->Stop(_effect);
}

std::unique_ptr<dxroids::EnemyShip> dxroids::Application::LaunchEnemyShip(EnemyShip::Size _size)
{
   std::unique_ptr<dxroids::EnemyShip> ship(new EnemyShip(sound_manager_, _size, Vector()));      

   Vector position;
   Vector velocity;

   Random rand;

   ScreenCorner corner = static_cast<ScreenCorner>(rand.Next(1, 5));
   velocity = VectorWithMagnitudeAndDirection(2.0f, DegreesToRadians(225.0f), DegreesToRadians(270.0f));

   switch (corner)
   {
   case ScreenCorner::TopLeft:
      position.SetX(0 - ship->BoundingRadius());
      position.SetY(ship->BoundingRadius());
      velocity = VectorWithMagnitudeAndDirection(2.0f, DegreesToRadians(90.0f), DegreesToRadians(135.0f));
      break;
   case ScreenCorner::TopRight:
      position.SetX(ScreenSize.x + ship->BoundingRadius());
      position.SetY(ship->BoundingRadius());
      velocity = VectorWithMagnitudeAndDirection(2.0f, DegreesToRadians(225.0f), DegreesToRadians(270.0f));
      break;
   case ScreenCorner::BottomLeft:
      position.SetX(0 - ship->BoundingRadius());
      position.SetY(ScreenSize.y - ship->BoundingRadius());
      velocity = VectorWithMagnitudeAndDirection(2.0f, DegreesToRadians(45.0f), DegreesToRadians(90.0f));
      break;
   case ScreenCorner::BottomRight:
      position.SetX(ScreenSize.x + ship->BoundingRadius());
      position.SetY(ScreenSize.y - ship->BoundingRadius());
      velocity = VectorWithMagnitudeAndDirection(2.0f, DegreesToRadians(270.0f), DegreesToRadians(315.0f));
      break;
   }

   ship->Initialize(GetDirect2DFactory());
   ship->SetIsActive(true);
   ship->SetPosition(position);
   ship->SetVelocity(velocity);

   return ship;
}

std::unique_ptr<dxroids::SpaceRock> dxroids::Application::NewRock()
{   
   Random rand;

   SpaceRock::Size size = SpaceRock::Size::Big;   
   SpaceRock::Style style = static_cast<SpaceRock::Style>(rand.Next(0, 3));
   ScreenEdge edge = static_cast<ScreenEdge>(rand.Next(1, 5));

   // get a random direction between 45 degrees and 135 dregres
   Vector velocity = VectorWithMagnitudeAndDirection(DXROIDS_SPACE_ROCK_SLOW_VELOCITY, 
      DXROIDS_MATH_PI_OVER_4, DXROIDS_MATH_PI_OVER_4 * 3);

   // we could create a big list of rocks and then just re-use them
   Vector position;   

   switch (edge)
   {
   case ScreenEdge::Top:
      velocity.Rotate(DXROIDS_MATH_PI_OVER_2);
      position.SetX(rand.NextFloat() * ScreenSize.x);
      position.SetY(-DXROIDS_SPACE_ROCK_OFFSCREEN_OFFSET);
      break;
   case ScreenEdge::Bottom:
      position.SetX(rand.NextFloat() * ScreenSize.x);
      position.SetY(ScreenSize.y + DXROIDS_SPACE_ROCK_OFFSCREEN_OFFSET);
      velocity.Rotate(-DXROIDS_MATH_PI_OVER_2);
      break;
   case ScreenEdge::Left:
      position.SetX(rand.NextFloat() * ScreenSize.y);
      position.SetY(-DXROIDS_SPACE_ROCK_OFFSCREEN_OFFSET);
      break;
   case ScreenEdge::Right:
      position.SetX(rand.NextFloat() * ScreenSize.y);
      position.SetY(ScreenSize.x + DXROIDS_SPACE_ROCK_OFFSCREEN_OFFSET);
      velocity.Rotate(DXROIDS_MATH_PI);
      break;
   default:
      DxRoidsAssert(false);
   }

   std::unique_ptr<SpaceRock> rock(new SpaceRock(position, size, style));
   rock->Initialize(GetDirect2DFactory());

   //float const rotational_velocity = RandomFloat(DXROIDS_SPACE_ROCK_ROTATIONAL_VELOCITY_MIN,
   //DXROIDS_SPACE_ROCK_ROTATIONAL_VELOCITY_MAX);      
   //rock->SetRotationalVelocity(rotational_velocity);   

   rock->SetIsActive(true);       
   rock->SetVelocity(velocity);

   return rock;
}

void dxroids::Application::HandleGamepadInput(XINPUT_GAMEPAD const & _gamepad_input)
{   
   static bool button_b_down = false;
   static bool button_y_down = false;
   static bool dpad_right_pressed = false;
   static bool dpad_left_pressed = false;

   if (_gamepad_input.wButtons & XINPUT_GAMEPAD_BACK)
   {
      PostQuitMessage(0);
   }

   if (!button_b_down && _gamepad_input.wButtons & XINPUT_GAMEPAD_B)
   {      
      button_b_down = true;
      if (player_ship_ != nullptr)
      {         
         player_ship_->FireShot(sound_manager_);         
      }
   }

   if (button_b_down && !(_gamepad_input.wButtons & XINPUT_GAMEPAD_B))
   {    
      button_b_down = false;
   }

   if (!dpad_left_pressed && _gamepad_input.wButtons & XINPUT_GAMEPAD_DPAD_LEFT)
   {
      dpad_left_pressed = true;
      dpad_right_pressed = false;      
      if (player_ship_ != nullptr)
      {
         player_ship_->RotateLeft();
      }
   }
   else if (dpad_left_pressed && !(_gamepad_input.wButtons & XINPUT_GAMEPAD_DPAD_LEFT))
   {      
      dpad_left_pressed = false;
      if (player_ship_ != nullptr)
      {
         player_ship_->StopRotate();
      }
   }

   // rotate right
   if (!dpad_right_pressed && _gamepad_input.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT)
   {      
      dpad_left_pressed = false;
      dpad_right_pressed = true;
      if (player_ship_ != nullptr)
      {
         player_ship_->RotateRight();
      }
   } 
   else if (dpad_right_pressed && !(_gamepad_input.wButtons & XINPUT_GAMEPAD_DPAD_RIGHT))
   {      
      dpad_right_pressed = false;
      if (player_ship_ != nullptr)
      {
         player_ship_->StopRotate();
      }
   }  

   if (!button_y_down && _gamepad_input.wButtons & XINPUT_GAMEPAD_Y)
   {
      button_y_down = true;
      if (player_ship_ != nullptr)
      {
         player_ship_->EnableThrust(sound_manager_);         
      }
   }

   if (button_y_down && !(_gamepad_input.wButtons & XINPUT_GAMEPAD_Y))
   {
      button_y_down = false;
      if (player_ship_ != nullptr)
      {
         player_ship_->DisableThrust(sound_manager_);         
      }
   }  
}

void dxroids::Application::OnKeyDown(WPARAM _param)
{
   if (_param == 'P')
   {
      if (game_state_.Current == GameState::Paused)
      {
         game_state_.Revert();
      }
      else
      {
         game_state_.Set(GameState::Paused);
      }
      return;
   }

   if (game_state_.Current == GameState::Paused)
      return;

   switch (_param)
   {   
   case 32: // space
      if (player_ship_ != nullptr)
      {
         player_ship_->EngageHyperspace();
      }
      break;
   case 'B': // 'b'
      if (enemy_ship_ == nullptr)
      {
         enemy_ship_ = LaunchEnemyShip(EnemyShip::Size::Large);
      }
      break;
   case 'S': // 's'
      if (enemy_ship_ == nullptr)
      {
         enemy_ship_ = LaunchEnemyShip(EnemyShip::Size::Small);
      }
      break;      
   case 'Z': // 'z'
      if (player_ship_ != nullptr)
      {
         player_ship_->FireShot(sound_manager_);         
      }
      break;
   case VK_LEFT:
      if (player_ship_ != nullptr)
      {
         player_ship_->RotateLeft();
      }
      break;
   case VK_RIGHT:   
      if (player_ship_ != nullptr)
      {
         player_ship_->RotateRight();
      }
      break;   
   case VK_UP:
      if (player_ship_ != nullptr)
      {         
         player_ship_->EnableThrust(sound_manager_);         
      }
      break;
   }
}

void dxroids::Application::OnKeyUp(WPARAM _param)
{   
   switch (_param)
   {
   case VK_LEFT:
   case VK_RIGHT:
      if (player_ship_)
      {
         player_ship_->StopRotate();
      }
      break;
   case VK_UP:
      if (player_ship_)
      {
         player_ship_->DisableThrust(sound_manager_);         
      }
      break;
   }   
}

void dxroids::Application::UpdateFrame(GameTime const & _timer)
{
   frame_counter_++;
   if (game_state_.Current == GameState::Paused)
   {
      return;
   }

   float const clip_extent_x = ScreenSize.x + 10.0f;
   float const clip_extent_y = ScreenSize.y + 10.0f;

   ClippingInformation clip_info = { clip_extent_x, clip_extent_y };

   bool wrap_vertical = false, wrap_horizontal = false;
   if (player_ship_ != nullptr)
   {               
      player_ship_->UpdateFrame(_timer, clip_info, &wrap_horizontal, &wrap_vertical);
   }

   if (enemy_ship_ != nullptr)
   {
      if (player_ship_ != nullptr)
      {         
         enemy_ship_->SetTarget(player_ship_->GetPosition());
      }      

      enemy_ship_->UpdateFrame(_timer, clip_info, &wrap_horizontal, &wrap_vertical);

      if (wrap_horizontal)
      {
         enemy_ship_.reset();
      }
   }

   for (std::unique_ptr<SpaceRock> & rock : rocks_)
   {
      rock->UpdateFrame(_timer, clip_info, &wrap_horizontal, &wrap_vertical);
   }

   // remove explosions that have run their course
   auto new_end = std::remove_if(explosions_.begin(), explosions_.end(), [=](std::unique_ptr<GameObject> & e)
   {
      return !e->IsActive();
   });
   if (new_end != explosions_.end())
   {
      explosions_.erase(new_end);
   }
   for (std::unique_ptr<GameObject> & explosion : explosions_)
   {
      explosion->UpdateFrame(_timer, clip_info, nullptr, nullptr);
   }

   if (rocks_.size() < 3)
   {
      // just for testing
      rocks_.push_back(std::move(NewRock()));
      rocks_.push_back(std::move(NewRock()));

      // for "free guy" this should be a bunch of times, re-use Object::RegisterTimedCallback code to do 
      // something like. re-factor that code into timer manager and re-use
      sound_manager_->Play(SoundEffect::ExtraShip);
   }
}

void dxroids::Application::DestroyShip(Ship & _ship)
{      
   std::unique_ptr<GameObject> explosion = _ship.Destroy(sound_manager_);
   if (explosion != nullptr)
   {
      explosions_.push_back(std::move(explosion));
   }
}

void dxroids::Application::CheckCollisions()
{   
   if (enemy_ship_  != nullptr && player_ship_  != nullptr && player_ship_->IntersectsWith(*enemy_ship_))
   {
      player_.AddToScore(*enemy_ship_);
      DestroyShip(*player_ship_);
      DestroyShip(*enemy_ship_);      
      player_ship_.reset();
      enemy_ship_.reset();      
   }

   if (enemy_ship_ != nullptr && player_ship_ != nullptr && player_ship_->IsShotHittingObject(*enemy_ship_))
   {
      player_.AddToScore(*enemy_ship_);
      DestroyShip(*enemy_ship_);
      enemy_ship_.reset();
   }

   if (enemy_ship_  != nullptr && player_ship_  != nullptr && enemy_ship_->IsShotHittingObject(*player_ship_))
   {
      player_ship_->Destroy(sound_manager_);
      explosions_.push_back(Explosion::NewDefault(player_ship_->GetPosition()));
      player_ship_.reset();
   }   

   std::vector<std::unique_ptr<SpaceRock>> new_rocks;
   for (std::unique_ptr<SpaceRock> & rock : rocks_)
   {                                 
      if (player_ship_ != nullptr)
      {
         CheckCollision(*player_ship_, *rock, new_rocks);
      }              
      if (enemy_ship_ != nullptr)
      {
         CheckCollision(*enemy_ship_, *rock, new_rocks);
      }
   }

   // remove dead rocks.
   auto const new_end = std::remove_if(rocks_.begin(), rocks_.end(), [&](std::unique_ptr<SpaceRock> & _rock)
   {
      return ! _rock->IsActive();
   });
   rocks_.erase(new_end, rocks_.end());

   std::move(new_rocks.begin(), new_rocks.end(), std::back_inserter(rocks_));   
}


void dxroids::Application::RunMessageLoop()
{
   int64_t delay_time_current = 0;
   int64_t delay_time_next = 0;

   XINPUT_STATE gamepad_state;

   GameTime timer;
   timer.Elapsed = GameTime::Now();

   int64_t const desired_frames_per_second = DXROIDS_APPLICATION_FRAMES_PER_SECOND;
   int64_t const perf_delay = GameTime::Frequency() / desired_frames_per_second;   

   QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&delay_time_current));
   delay_time_next = delay_time_current + perf_delay;        

   MSG msg;
   PeekMessage(&msg, nullptr, 0, 0, PM_NOREMOVE);

   while (msg.message != WM_QUIT)
   {      
      if (PeekMessage(&msg, nullptr, 0, 0, PM_REMOVE))
      {
         if (msg.message == WM_KEYDOWN)
         {
            OnKeyDown(msg.wParam);            
         }
         else if (msg.message == WM_KEYUP)
         {
            OnKeyUp(msg.wParam);            
         }

         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
      else
      {
         if (gamepad_user_index_ != -1)
         {
            if (ERROR_SUCCESS == XInputGetState(gamepad_user_index_, &gamepad_state))
            {
               this->HandleGamepadInput(gamepad_state.Gamepad);               
            }
         }

         QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&delay_time_current));
         timer.Elapsed = delay_time_current;

         if (delay_time_current > delay_time_next)
         {
            if (game_state_.Current != GameState::Paused)
            {
               if (next_thump_++ == thump_interval_)
               {
                  sound_manager_->Play(SoundEffect::BeatOne, false);               
               }
               else if (next_thump_++ == (thump_interval_ * 2))
               {
                  sound_manager_->Play(SoundEffect::BeatTwo, false);               
                  next_thump_ = 0;
               }
            }

            UpdateFrame(timer);
            CheckCollisions();

            // trigger redraw
            delay_time_next = delay_time_current + perf_delay;
            InvalidateRect(window_handle_, NULL, FALSE);         
         }
         else
         {
            //Sleep(1);
         }
      }
   }
}

HRESULT dxroids::Application::CreateDeviceIndependentResources()
{
   return S_OK;
}

HRESULT dxroids::Application::CreateDeviceResources()
{
   if (render_target_ != nullptr)
   {
      return S_OK;
   }

   RECT rect;
   GetClientRect(window_handle_, &rect);

   D2D1_SIZE_U const size = D2D1::SizeU(rect.right - rect.left, rect.bottom - rect.top);      

   ThrowIfFailed(
      GetDirect2DFactory()->CreateHwndRenderTarget(D2D1::RenderTargetProperties(),
      D2D1::HwndRenderTargetProperties(window_handle_, size), &render_target_));

   ThrowIfFailed(
      render_target_->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::CadetBlue),
      &primary_brush_));   

   ThrowIfFailed(
      render_target_->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::CornflowerBlue),
      &secondary_brush_));   

   ThrowIfFailed(
      render_target_->CreateSolidColorBrush(D2D1::ColorF(D2D1::ColorF::DarkRed), 
      &debug_brush_));   

   return S_OK;
}

void dxroids::Application::DiscardDeviceResources()
{
   render_target_ = nullptr;
   primary_brush_ = nullptr;
   secondary_brush_ = nullptr;
}

HRESULT dxroids::Application::OnRender()
{   
   HRESULT const result = CreateDeviceResources();
   if (D2DERR_RECREATE_TARGET == result)
   {      
      DiscardDeviceResources();
      return S_OK;
   }

   ThrowIfFailed(result);  
      
   D2D1_SIZE_F const size = render_target_->GetSize();
   render_target_->BeginDraw();
   render_target_->SetTransform(D2D1::Matrix3x2F::Identity());
   render_target_->Clear(D2D1::ColorF(D2D1::ColorF::Black));

   RenderContext const render_context = { render_target_, primary_brush_, debug_brush_, DebugRenderFlags::None };

   if (player_ship_ != nullptr)
   {
      render_target_->SetTransform(D2D1::Matrix3x2F::Identity());
      player_ship_->Render(render_context);          
   }

   if (enemy_ship_ != nullptr)
   {
      render_target_->SetTransform(D2D1::Matrix3x2F::Identity());
      enemy_ship_->Render(render_context);
   }

   for (std::unique_ptr<SpaceRock> & rock : rocks_)
   {
      rock->Render(render_context);
   }

   // draw player score
   {
      D2D_POINT_2F score_position;

      _snprintf_s(draw_score_buffer_, sizeof(draw_score_buffer_), "%02d", player_.GetScore());
      render_target_->SetTransform(D2D1::Matrix3x2F::Identity());

      score_position.x = ScreenSize.x / 4;
      score_position.y = 10.0f;
      char_renderer_.DrawString(render_target_, primary_brush_, score_position, draw_score_buffer_, 5.0f,
         CharacterRenderer::Anchor::Right);

      _snprintf_s(draw_score_buffer_, sizeof(draw_score_buffer_), "%02d", high_score_);
      render_target_->SetTransform(D2D1::Matrix3x2F::Identity());

      score_position.x = ScreenSize.x / 2.0f;
      score_position.y = 10.0f;

      char_renderer_.DrawString(render_target_, primary_brush_, score_position, draw_score_buffer_, 2.5f,
         CharacterRenderer::Anchor::Center);
   }

   for (std::unique_ptr<PlayerShip> & ship : remaining_ships_)
   {
      ship->Render(render_context);
   }

#if 0
   for (int x = 0, n = static_cast<int>(size.width); x < n; x += 10)
   {
      d2d_render_target_->DrawLine(D2D1::Point2F(static_cast<FLOAT>(x), 0.0f),
         D2D1::Point2F(static_cast<FLOAT>(x), size.height), grey_brush_);
   }

   for (int y = 0, n = static_cast<int>(size.height); y < n; y += 10)
   {
      d2d_render_target_->DrawLine(D2D1::Point2F(0.0f, static_cast<FLOAT>(y)),
         D2D1::Point2F(size.width, static_cast<FLOAT>(y)), cornflower_brush_);
   }
#endif

   for (std::unique_ptr<GameObject> & explosion : explosions_)
   {
      explosion->Render(render_context);      
   }

   if (game_state_.Current == GameState::Paused)
   {      
      D2D_POINT_2F const pause_position = { ScreenSize.x / 2.0f, ScreenSize.y / 2.0f };
      render_target_->SetTransform(D2D1::Matrix3x2F::Identity());
      char_renderer_.DrawString(render_target_, primary_brush_, pause_position, "PAUSED", 6.0f,
         CharacterRenderer::Anchor::Center);
   }

   return render_target_->EndDraw();   
}

void dxroids::Application::OnResize(UINT _width, UINT _height)
{   
   if (render_target_)
   {
      // Note: This method can fail, but it's okay to ignore the
      // error here, because the error will be returned again
      // the next time EndDraw is called.
      render_target_->Resize(D2D1::SizeU(_width, _height));
   }

   ScreenSize.x = static_cast<float>(_width);
   ScreenSize.y = static_cast<float>(_height);
}

LRESULT CALLBACK dxroids::Application::MessageHandler(HWND _window_handle, UINT _message, 
                                                      WPARAM _w_param, LPARAM _l_param)
{
   LRESULT result = 0;

   if (_message == WM_CREATE)
   {
      LPCREATESTRUCT create_struct = reinterpret_cast<LPCREATESTRUCT>(_l_param);
      dxroids::Application * app = reinterpret_cast<dxroids::Application *>(create_struct->lpCreateParams);

      SetWindowLongPtr(_window_handle, GWLP_USERDATA, PtrToUlong(app));
   }
   else
   {      
      dxroids::Application * app = reinterpret_cast<dxroids::Application *>(static_cast<LONG_PTR>(
         GetWindowLongPtr(_window_handle, GWLP_USERDATA)));

      bool was_handled = false;
      if (app)
      {
         switch (_message)
         {
         case WM_COMMAND:
            switch (LOWORD(_w_param))
            {
            case IDM_ABOUT:
               DialogBox(HINST_THISCOMPONENT, MAKEINTRESOURCE(IDD_ABOUTBOX), _window_handle, About);
               break;
            case IDM_EXIT:
               DestroyWindow(_window_handle);
               break;
            }
            break;

         case WM_SIZE:
            app->OnResize(LOWORD(_l_param), HIWORD(_l_param));
            was_handled = true;
            break;

         case WM_DISPLAYCHANGE:
            InvalidateRect(_window_handle, nullptr, FALSE);
            was_handled = true;
            break;

         case WM_PAINT:
            app->OnRender();
            ValidateRect(_window_handle, nullptr);
            was_handled = true;
            break;

         case WM_DESTROY:
            PostQuitMessage(0);
            was_handled = true;
            result = 1;
            break;

         }
      }

      if (!was_handled)
      {
         result = DefWindowProc(_window_handle, _message, _w_param, _l_param);
      }
   }
   return result;
}
